/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
/**
 * Class: mxGraphView
 *
 * Extends <mxEventSource> to implement a view for a graph. This class is in
 * charge of computing the absolute coordinates for the relative child
 * geometries, the points for perimeters and edge styles and keeping them
 * cached in <mxCellStates> for faster retrieval. The states are updated
 * whenever the model or the view state (translate, scale) changes. The scale
 * and translate are honoured in the bounds.
 *
 * Event: mxEvent.UNDO
 *
 * Fires after the root was changed in <setCurrentRoot>. The <code>edit</code>
 * property contains the <mxUndoableEdit> which contains the
 * <mxCurrentRootChange>.
 *
 * Event: mxEvent.SCALE_AND_TRANSLATE
 *
 * Fires after the scale and translate have been changed in <scaleAndTranslate>.
 * The <code>scale</code>, <code>previousScale</code>, <code>translate</code>
 * and <code>previousTranslate</code> properties contain the new and previous
 * scale and translate, respectively.
 *
 * Event: mxEvent.SCALE
 *
 * Fires after the scale was changed in <setScale>. The <code>scale</code> and
 * <code>previousScale</code> properties contain the new and previous scale.
 *
 * Event: mxEvent.TRANSLATE
 *
 * Fires after the translate was changed in <setTranslate>. The
 * <code>translate</code> and <code>previousTranslate</code> properties contain
 * the new and previous value for translate.
 *
 * Event: mxEvent.DOWN and mxEvent.UP
 *
 * Fire if the current root is changed by executing an <mxCurrentRootChange>.
 * The event name depends on the location of the root in the cell hierarchy
 * with respect to the current root. The <code>root</code> and
 * <code>previous</code> properties contain the new and previous root,
 * respectively.
 *
 * Constructor: mxGraphView
 *
 * Constructs a new view for the given <mxGraph>.
 *
 * Parameters:
 *
 * graph - Reference to the enclosing <mxGraph>.
 */
function mxGraphView(graph) {
    this.graph = graph;
    this.translate = new mxPoint();
    this.graphBounds = new mxRectangle();
    this.states = new mxDictionary();
};

/**
 * Extends mxEventSource.
 */
mxGraphView.prototype = new mxEventSource();
mxGraphView.prototype.constructor = mxGraphView;

/**
 *
 */
mxGraphView.prototype.EMPTY_POINT = new mxPoint();

/**
 * Variable: doneResource
 *
 * Specifies the resource key for the status message after a long operation.
 * If the resource for this key does not exist then the value is used as
 * the status message. Default is 'done'.
 */
mxGraphView.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';

/**
 * Function: updatingDocumentResource
 *
 * Specifies the resource key for the status message while the document is
 * being updated. If the resource for this key does not exist then the
 * value is used as the status message. Default is 'updatingDocument'.
 */
mxGraphView.prototype.updatingDocumentResource = (mxClient.language != 'none') ? 'updatingDocument' : '';

/**
 * Variable: allowEval
 *
 * Specifies if string values in cell styles should be evaluated using
 * <mxUtils.eval>. This will only be used if the string values can't be mapped
 * to objects using <mxStyleRegistry>. Default is false. NOTE: Enabling this
 * switch carries a possible security risk.
 */
mxGraphView.prototype.allowEval = false;

/**
 * Variable: captureDocumentGesture
 *
 * Specifies if a gesture should be captured when it goes outside of the
 * graph container. Default is true.
 */
mxGraphView.prototype.captureDocumentGesture = true;

/**
 * Variable: optimizeVmlReflows
 *
 * Specifies if the <canvas> should be hidden while rendering in IE8 standards
 * mode and quirks mode. This will significantly improve rendering performance.
 * Default is true.
 */
mxGraphView.prototype.optimizeVmlReflows = true;

/**
 * Variable: rendering
 *
 * Specifies if shapes should be created, updated and destroyed using the
 * methods of <mxCellRenderer> in <graph>. Default is true.
 */
mxGraphView.prototype.rendering = true;

/**
 * Variable: graph
 *
 * Reference to the enclosing <mxGraph>.
 */
mxGraphView.prototype.graph = null;

/**
 * Variable: currentRoot
 *
 * <mxCell> that acts as the root of the displayed cell hierarchy.
 */
mxGraphView.prototype.currentRoot = null;

/**
 * Variable: graphBounds
 *
 * <mxRectangle> that caches the scales, translated bounds of the current view.
 */
mxGraphView.prototype.graphBounds = null;

/**
 * Variable: scale
 *
 * Specifies the scale. Default is 1 (100%).
 */
mxGraphView.prototype.scale = 1;

/**
 * Variable: translate
 *
 * <mxPoint> that specifies the current translation. Default is a new
 * empty <mxPoint>.
 */
mxGraphView.prototype.translate = null;

/**
 * Variable: states
 *
 * <mxDictionary> that maps from cell IDs to <mxCellStates>.
 */
mxGraphView.prototype.states = null;

/**
 * Variable: updateStyle
 *
 * Specifies if the style should be updated in each validation step. If this
 * is false then the style is only updated if the state is created or if the
 * style of the cell was changed. Default is false.
 */
mxGraphView.prototype.updateStyle = false;

/**
 * Variable: lastNode
 *
 * During validation, this contains the last DOM node that was processed.
 */
mxGraphView.prototype.lastNode = null;

/**
 * Variable: lastHtmlNode
 *
 * During validation, this contains the last HTML DOM node that was processed.
 */
mxGraphView.prototype.lastHtmlNode = null;

/**
 * Variable: lastForegroundNode
 *
 * During validation, this contains the last edge's DOM node that was processed.
 */
mxGraphView.prototype.lastForegroundNode = null;

/**
 * Variable: lastForegroundHtmlNode
 *
 * During validation, this contains the last edge HTML DOM node that was processed.
 */
mxGraphView.prototype.lastForegroundHtmlNode = null;

/**
 * Function: getGraphBounds
 *
 * Returns <graphBounds>.
 */
mxGraphView.prototype.getGraphBounds = function () {
    return this.graphBounds;
};

/**
 * Function: setGraphBounds
 *
 * Sets <graphBounds>.
 */
mxGraphView.prototype.setGraphBounds = function (value) {
    this.graphBounds = value;
};

/**
 * Function: getBounds
 *
 * Returns the union of all <mxCellStates> for the given array of <mxCells>.
 *
 * Parameters:
 *
 * cells - Array of <mxCells> whose bounds should be returned.
 */
mxGraphView.prototype.getBounds = function (cells) {
    var result = null;

    if (cells != null && cells.length > 0) {
        var model = this.graph.getModel();

        for (var i = 0; i < cells.length; i++) {
            if (model.isVertex(cells[i]) || model.isEdge(cells[i])) {
                var state = this.getState(cells[i]);

                if (state != null) {
                    if (result == null) {
                        result = mxRectangle.fromRectangle(state);
                    }
                    else {
                        result.add(state);
                    }
                }
            }
        }
    }

    return result;
};

/**
 * Function: setCurrentRoot
 *
 * Sets and returns the current root and fires an <undo> event before
 * calling <mxGraph.sizeDidChange>.
 *
 * Parameters:
 *
 * root - <mxCell> that specifies the root of the displayed cell hierarchy.
 */
mxGraphView.prototype.setCurrentRoot = function (root) {
    if (this.currentRoot != root) {
        var change = new mxCurrentRootChange(this, root);
        change.execute();
        var edit = new mxUndoableEdit(this, true);
        edit.add(change);
        this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
        this.graph.sizeDidChange();
    }

    return root;
};

/**
 * Function: scaleAndTranslate
 *
 * Sets the scale and translation and fires a <scale> and <translate> event
 * before calling <revalidate> followed by <mxGraph.sizeDidChange>.
 *
 * Parameters:
 *
 * scale - Decimal value that specifies the new scale (1 is 100%).
 * dx - X-coordinate of the translation.
 * dy - Y-coordinate of the translation.
 */
mxGraphView.prototype.scaleAndTranslate = function (scale, dx, dy) {
    var previousScale = this.scale;
    var previousTranslate = new mxPoint(this.translate.x, this.translate.y);

    if (this.scale != scale || this.translate.x != dx || this.translate.y != dy) {
        this.scale = scale;

        this.translate.x = dx;
        this.translate.y = dy;

        if (this.isEventsEnabled()) {
            this.viewStateChanged();
        }
    }

    this.fireEvent(new mxEventObject(mxEvent.SCALE_AND_TRANSLATE,
        'scale', scale, 'previousScale', previousScale,
        'translate', this.translate, 'previousTranslate', previousTranslate));
};

/**
 * Function: getScale
 *
 * Returns the <scale>.
 */
mxGraphView.prototype.getScale = function () {
    return this.scale;
};

/**
 * Function: setScale
 *
 * Sets the scale and fires a <scale> event before calling <revalidate> followed
 * by <mxGraph.sizeDidChange>.
 *
 * Parameters:
 *
 * value - Decimal value that specifies the new scale (1 is 100%).
 */
mxGraphView.prototype.setScale = function (value) {
    var previousScale = this.scale;

    if (this.scale != value) {
        this.scale = value;

        if (this.isEventsEnabled()) {
            this.viewStateChanged();
        }
    }

    this.fireEvent(new mxEventObject(mxEvent.SCALE,
        'scale', value, 'previousScale', previousScale));
};

/**
 * Function: getTranslate
 *
 * Returns the <translate>.
 */
mxGraphView.prototype.getTranslate = function () {
    return this.translate;
};

/**
 * Function: setTranslate
 *
 * Sets the translation and fires a <translate> event before calling
 * <revalidate> followed by <mxGraph.sizeDidChange>. The translation is the
 * negative of the origin.
 *
 * Parameters:
 *
 * dx - X-coordinate of the translation.
 * dy - Y-coordinate of the translation.
 */
mxGraphView.prototype.setTranslate = function (dx, dy) {
    var previousTranslate = new mxPoint(this.translate.x, this.translate.y);

    if (this.translate.x != dx || this.translate.y != dy) {
        this.translate.x = dx;
        this.translate.y = dy;

        if (this.isEventsEnabled()) {
            this.viewStateChanged();
        }
    }

    this.fireEvent(new mxEventObject(mxEvent.TRANSLATE,
        'translate', this.translate, 'previousTranslate', previousTranslate));
};

/**
 * Function: viewStateChanged
 *
 * Invoked after <scale> and/or <translate> has changed.
 */
mxGraphView.prototype.viewStateChanged = function () {
    this.revalidate();
    this.graph.sizeDidChange();
};

/**
 * Function: refresh
 *
 * Clears the view if <currentRoot> is not null and revalidates.
 */
mxGraphView.prototype.refresh = function () {
    if (this.currentRoot != null) {
        this.clear();
    }

    this.revalidate();
};

/**
 * Function: revalidate
 *
 * Revalidates the complete view with all cell states.
 */
mxGraphView.prototype.revalidate = function () {
    this.invalidate();
    this.validate();
};

/**
 * Function: clear
 *
 * Removes the state of the given cell and all descendants if the given
 * cell is not the current root.
 *
 * Parameters:
 *
 * cell - Optional <mxCell> for which the state should be removed. Default
 * is the root of the model.
 * force - Boolean indicating if the current root should be ignored for
 * recursion.
 */
mxGraphView.prototype.clear = function (cell, force, recurse) {
    var model = this.graph.getModel();
    cell = cell || model.getRoot();
    force = (force != null) ? force : false;
    recurse = (recurse != null) ? recurse : true;

    this.removeState(cell);

    if (recurse && (force || cell != this.currentRoot)) {
        var childCount = model.getChildCount(cell);

        for (var i = 0; i < childCount; i++) {
            this.clear(model.getChildAt(cell, i), force);
        }
    }
    else {
        this.invalidate(cell);
    }
};

/**
 * Function: invalidate
 *
 * Invalidates the state of the given cell, all its descendants and
 * connected edges.
 *
 * Parameters:
 *
 * cell - Optional <mxCell> to be invalidated. Default is the root of the
 * model.
 */
mxGraphView.prototype.invalidate = function (cell, recurse, includeEdges) {
    var model = this.graph.getModel();
    cell = cell || model.getRoot();
    recurse = (recurse != null) ? recurse : true;
    includeEdges = (includeEdges != null) ? includeEdges : true;

    var state = this.getState(cell);

    if (state != null) {
        state.invalid = true;
    }

    // Avoids infinite loops for invalid graphs
    if (!cell.invalidating) {
        cell.invalidating = true;

        // Recursively invalidates all descendants
        if (recurse) {
            var childCount = model.getChildCount(cell);

            for (var i = 0; i < childCount; i++) {
                var child = model.getChildAt(cell, i);
                this.invalidate(child, recurse, includeEdges);
            }
        }

        // Propagates invalidation to all connected edges
        if (includeEdges) {
            var edgeCount = model.getEdgeCount(cell);

            for (var i = 0; i < edgeCount; i++) {
                this.invalidate(model.getEdgeAt(cell, i), recurse, includeEdges);
            }
        }

        delete cell.invalidating;
    }
};

/**
 * Function: validate
 *
 * Calls <validateCell> and <validateCellState> and updates the <graphBounds>
 * using <getBoundingBox>. Finally the background is validated using
 * <validateBackground>.
 *
 * Parameters:
 *
 * cell - Optional <mxCell> to be used as the root of the validation.
 * Default is <currentRoot> or the root of the model.
 */
mxGraphView.prototype.validate = function (cell) {
    var t0 = mxLog.enter('mxGraphView.validate');
    window.status = mxResources.get(this.updatingDocumentResource) ||
        this.updatingDocumentResource;

    this.resetValidationState();

    // Improves IE rendering speed by minimizing reflows
    var prevDisplay = null;

    if (this.optimizeVmlReflows && this.canvas != null && this.textDiv == null &&
        ((document.documentMode == 8 && !mxClient.IS_EM) || mxClient.IS_QUIRKS)) {
        // Placeholder keeps scrollbar positions when canvas is hidden
        this.placeholder = document.createElement('div');
        this.placeholder.style.position = 'absolute';
        this.placeholder.style.width = this.canvas.clientWidth + 'px';
        this.placeholder.style.height = this.canvas.clientHeight + 'px';
        this.canvas.parentNode.appendChild(this.placeholder);

        prevDisplay = this.drawPane.style.display;
        this.canvas.style.display = 'none';

        // Creates temporary DIV used for text measuring in mxText.updateBoundingBox
        this.textDiv = document.createElement('div');
        this.textDiv.style.position = 'absolute';
        this.textDiv.style.whiteSpace = 'nowrap';
        this.textDiv.style.visibility = 'hidden';
        this.textDiv.style.display = (mxClient.IS_QUIRKS) ? 'inline' : 'inline-block';
        this.textDiv.style.zoom = '1';

        document.body.appendChild(this.textDiv);
    }

    var graphBounds = this.getBoundingBox(this.validateCellState(
        this.validateCell(cell || ((this.currentRoot != null) ?
            this.currentRoot : this.graph.getModel().getRoot()))));
    this.setGraphBounds((graphBounds != null) ? graphBounds : this.getEmptyBounds());
    this.validateBackground();

    if (prevDisplay != null) {
        this.canvas.style.display = prevDisplay;
        this.textDiv.parentNode.removeChild(this.textDiv);

        if (this.placeholder != null) {
            this.placeholder.parentNode.removeChild(this.placeholder);
        }

        // Textdiv cannot be reused
        this.textDiv = null;
    }

    this.resetValidationState();

    window.status = mxResources.get(this.doneResource) ||
        this.doneResource;
    mxLog.leave('mxGraphView.validate', t0);
};

/**
 * Function: getEmptyBounds
 *
 * Returns the bounds for an empty graph. This returns a rectangle at
 * <translate> with the size of 0 x 0.
 */
mxGraphView.prototype.getEmptyBounds = function () {
    return new mxRectangle(this.translate.x * this.scale, this.translate.y * this.scale);
};

/**
 * Function: getBoundingBox
 *
 * Returns the bounding box of the shape and the label for the given
 * <mxCellState> and its children if recurse is true.
 *
 * Parameters:
 *
 * state - <mxCellState> whose bounding box should be returned.
 * recurse - Optional boolean indicating if the children should be included.
 * Default is true.
 */
mxGraphView.prototype.getBoundingBox = function (state, recurse) {
    recurse = (recurse != null) ? recurse : true;
    var bbox = null;

    if (state != null) {
        if (state.shape != null && state.shape.boundingBox != null) {
            bbox = state.shape.boundingBox.clone();
        }

        // Adds label bounding box to graph bounds
        if (state.text != null && state.text.boundingBox != null) {
            if (bbox != null) {
                bbox.add(state.text.boundingBox);
            }
            else {
                bbox = state.text.boundingBox.clone();
            }
        }

        if (recurse) {
            var model = this.graph.getModel();
            var childCount = model.getChildCount(state.cell);

            for (var i = 0; i < childCount; i++) {
                var bounds = this.getBoundingBox(this.getState(model.getChildAt(state.cell, i)));

                if (bounds != null) {
                    if (bbox == null) {
                        bbox = bounds;
                    }
                    else {
                        bbox.add(bounds);
                    }
                }
            }
        }
    }

    return bbox;
};

/**
 * Function: createBackgroundPageShape
 *
 * Creates and returns the shape used as the background page.
 *
 * Parameters:
 *
 * bounds - <mxRectangle> that represents the bounds of the shape.
 */
mxGraphView.prototype.createBackgroundPageShape = function (bounds) {
    return new mxRectangleShape(bounds, 'white', 'black');
};

/**
 * Function: validateBackground
 *
 * Calls <validateBackgroundImage> and <validateBackgroundPage>.
 */
mxGraphView.prototype.validateBackground = function () {
    this.validateBackgroundImage();
    this.validateBackgroundPage();
};

/**
 * Function: validateBackgroundImage
 *
 * Validates the background image.
 */
mxGraphView.prototype.validateBackgroundImage = function () {
    var bg = this.graph.getBackgroundImage();

    if (bg != null) {
        if (this.backgroundImage == null || this.backgroundImage.image != bg.src) {
            if (this.backgroundImage != null) {
                this.backgroundImage.destroy();
            }

            var bounds = new mxRectangle(0, 0, 1, 1);

            this.backgroundImage = new mxImageShape(bounds, bg.src);
            this.backgroundImage.dialect = this.graph.dialect;
            this.backgroundImage.init(this.backgroundPane);
            this.backgroundImage.redraw();

            // Workaround for ignored event on background in IE8 standards mode
            if (document.documentMode == 8 && !mxClient.IS_EM) {
                mxEvent.addGestureListeners(this.backgroundImage.node,
                    mxUtils.bind(this, function (evt) {
                        this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
                    }),
                    mxUtils.bind(this, function (evt) {
                        this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
                    }),
                    mxUtils.bind(this, function (evt) {
                        this.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
                    })
                );
            }
        }

        this.redrawBackgroundImage(this.backgroundImage, bg);
    }
    else if (this.backgroundImage != null) {
        this.backgroundImage.destroy();
        this.backgroundImage = null;
    }
};

/**
 * Function: validateBackgroundPage
 *
 * Validates the background page.
 */
mxGraphView.prototype.validateBackgroundPage = function () {
    if (this.graph.pageVisible) {
        var bounds = this.getBackgroundPageBounds();

        if (this.backgroundPageShape == null) {
            this.backgroundPageShape = this.createBackgroundPageShape(bounds);
            this.backgroundPageShape.scale = this.scale;
            this.backgroundPageShape.isShadow = true;
            this.backgroundPageShape.dialect = this.graph.dialect;
            this.backgroundPageShape.init(this.backgroundPane);
            this.backgroundPageShape.redraw();

            // Adds listener for double click handling on background
            if (this.graph.nativeDblClickEnabled) {
                mxEvent.addListener(this.backgroundPageShape.node, 'dblclick', mxUtils.bind(this, function (evt) {
                    this.graph.dblClick(evt);
                }));
            }

            // Adds basic listeners for graph event dispatching outside of the
            // container and finishing the handling of a single gesture
            mxEvent.addGestureListeners(this.backgroundPageShape.node,
                mxUtils.bind(this, function (evt) {
                    this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
                }),
                mxUtils.bind(this, function (evt) {
                    // Hides the tooltip if mouse is outside container
                    if (this.graph.tooltipHandler != null && this.graph.tooltipHandler.isHideOnHover()) {
                        this.graph.tooltipHandler.hide();
                    }

                    if (this.graph.isMouseDown && !mxEvent.isConsumed(evt)) {
                        this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
                    }
                }),
                mxUtils.bind(this, function (evt) {
                    this.graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
                })
            );
        }
        else {
            this.backgroundPageShape.scale = this.scale;
            this.backgroundPageShape.bounds = bounds;
            this.backgroundPageShape.redraw();
        }
    }
    else if (this.backgroundPageShape != null) {
        this.backgroundPageShape.destroy();
        this.backgroundPageShape = null;
    }
};

/**
 * Function: getBackgroundPageBounds
 *
 * Returns the bounds for the background page.
 */
mxGraphView.prototype.getBackgroundPageBounds = function () {
    var fmt = this.graph.pageFormat;
    var ps = this.scale * this.graph.pageScale;
    var bounds = new mxRectangle(this.scale * this.translate.x, this.scale * this.translate.y,
        fmt.width * ps, fmt.height * ps);

    return bounds;
};

/**
 * Function: redrawBackgroundImage
 *
 * Updates the bounds and redraws the background image.
 *
 * Example:
 *
 * If the background image should not be scaled, this can be replaced with
 * the following.
 *
 * (code)
 * mxGraphView.prototype.redrawBackground = function(backgroundImage, bg)
 * {
 *   backgroundImage.bounds.x = this.translate.x;
 *   backgroundImage.bounds.y = this.translate.y;
 *   backgroundImage.bounds.width = bg.width;
 *   backgroundImage.bounds.height = bg.height;
 *
 *   backgroundImage.redraw();
 * };
 * (end)
 *
 * Parameters:
 *
 * backgroundImage - <mxImageShape> that represents the background image.
 * bg - <mxImage> that specifies the image and its dimensions.
 */
mxGraphView.prototype.redrawBackgroundImage = function (backgroundImage, bg) {
    backgroundImage.scale = this.scale;
    backgroundImage.bounds.x = this.scale * this.translate.x;
    backgroundImage.bounds.y = this.scale * this.translate.y;
    backgroundImage.bounds.width = this.scale * bg.width;
    backgroundImage.bounds.height = this.scale * bg.height;

    backgroundImage.redraw();
};

/**
 * Function: validateCell
 *
 * Recursively creates the cell state for the given cell if visible is true and
 * the given cell is visible. If the cell is not visible but the state exists
 * then it is removed using <removeState>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose <mxCellState> should be created.
 * visible - Optional boolean indicating if the cell should be visible. Default
 * is true.
 */
mxGraphView.prototype.validateCell = function (cell, visible) {
    visible = (visible != null) ? visible : true;

    if (cell != null) {
        visible = visible && this.graph.isCellVisible(cell);
        var state = this.getState(cell, visible);

        if (state != null && !visible) {
            this.removeState(cell);
        }
        else {
            var model = this.graph.getModel();
            var childCount = model.getChildCount(cell);

            for (var i = 0; i < childCount; i++) {
                this.validateCell(model.getChildAt(cell, i), visible &&
                    (!this.isCellCollapsed(cell) || cell == this.currentRoot));
            }
        }
    }

    return cell;
};

/**
 * Function: validateCellState
 *
 * Validates and repaints the <mxCellState> for the given <mxCell>.
 *
 * Parameters:
 *
 * cell - <mxCell> whose <mxCellState> should be validated.
 * recurse - Optional boolean indicating if the children of the cell should be
 * validated. Default is true.
 */
mxGraphView.prototype.validateCellState = function (cell, recurse) {
    recurse = (recurse != null) ? recurse : true;
    var state = null;

    if (cell != null) {
        state = this.getState(cell);

        if (state != null) {
            var model = this.graph.getModel();

            if (state.invalid) {
                state.invalid = false;

                if (state.style == null || state.invalidStyle) {
                    state.style = this.graph.getCellStyle(state.cell);
                    state.invalidStyle = false;
                }

                if (cell != this.currentRoot) {
                    this.validateCellState(model.getParent(cell), false);
                }

                state.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, true), false), true);
                state.setVisibleTerminalState(this.validateCellState(this.getVisibleTerminal(cell, false), false), false);

                this.updateCellState(state);

                // Repaint happens immediately after the cell is validated
                if (cell != this.currentRoot && !state.invalid) {
                    this.graph.cellRenderer.redraw(state, false, this.isRendering());

                    // Handles changes to invertex paintbounds after update of rendering shape
                    state.updateCachedBounds();
                }
            }

            if (recurse && !state.invalid) {
                // Updates order in DOM if recursively traversing
                if (state.shape != null) {
                    this.stateValidated(state);
                }

                var childCount = model.getChildCount(cell);

                for (var i = 0; i < childCount; i++) {
                    this.validateCellState(model.getChildAt(cell, i));
                }
            }
        }
    }

    return state;
};

/**
 * Function: updateCellState
 *
 * Updates the given <mxCellState>.
 *
 * Parameters:
 *
 * state - <mxCellState> to be updated.
 */
mxGraphView.prototype.updateCellState = function (state) {
    state.absoluteOffset.x = 0;
    state.absoluteOffset.y = 0;
    state.origin.x = 0;
    state.origin.y = 0;
    state.length = 0;

    if (state.cell != this.currentRoot) {
        var model = this.graph.getModel();
        var pState = this.getState(model.getParent(state.cell));

        if (pState != null && pState.cell != this.currentRoot) {
            state.origin.x += pState.origin.x;
            state.origin.y += pState.origin.y;
        }

        var offset = this.graph.getChildOffsetForCell(state.cell);

        if (offset != null) {
            state.origin.x += offset.x;
            state.origin.y += offset.y;
        }

        var geo = this.graph.getCellGeometry(state.cell);

        if (geo != null) {
            if (!model.isEdge(state.cell)) {
                offset = (geo.offset != null) ? geo.offset : this.EMPTY_POINT;

                if (geo.relative && pState != null) {
                    if (model.isEdge(pState.cell)) {
                        var origin = this.getPoint(pState, geo);

                        if (origin != null) {
                            state.origin.x += (origin.x / this.scale) - pState.origin.x - this.translate.x;
                            state.origin.y += (origin.y / this.scale) - pState.origin.y - this.translate.y;
                        }
                    }
                    else {
                        state.origin.x += geo.x * pState.unscaledWidth + offset.x;
                        state.origin.y += geo.y * pState.unscaledHeight + offset.y;
                    }
                }
                else {
                    state.absoluteOffset.x = this.scale * offset.x;
                    state.absoluteOffset.y = this.scale * offset.y;
                    state.origin.x += geo.x;
                    state.origin.y += geo.y;
                }
            }

            state.x = this.scale * (this.translate.x + state.origin.x);
            state.y = this.scale * (this.translate.y + state.origin.y);
            state.width = this.scale * geo.width;
            state.unscaledWidth = geo.width;
            state.height = this.scale * geo.height;
            state.unscaledHeight = geo.height;

            if (model.isVertex(state.cell)) {
                this.updateVertexState(state, geo);
            }

            if (model.isEdge(state.cell)) {
                this.updateEdgeState(state, geo);
            }
        }
    }

    state.updateCachedBounds();
};

/**
 * Function: isCellCollapsed
 *
 * Returns true if the children of the given cell should not be visible in the
 * view. This implementation uses <mxGraph.isCellVisible> but it can be
 * overidden to use a separate condition.
 */
mxGraphView.prototype.isCellCollapsed = function (cell) {
    return this.graph.isCellCollapsed(cell);
};

/**
 * Function: updateVertexState
 *
 * Validates the given cell state.
 */
mxGraphView.prototype.updateVertexState = function (state, geo) {
    var model = this.graph.getModel();
    var pState = this.getState(model.getParent(state.cell));

    if (geo.relative && pState != null && !model.isEdge(pState.cell)) {
        var alpha = mxUtils.toRadians(pState.style[mxConstants.STYLE_ROTATION] || '0');

        if (alpha != 0) {
            var cos = Math.cos(alpha);
            var sin = Math.sin(alpha);

            var ct = new mxPoint(state.getCenterX(), state.getCenterY());
            var cx = new mxPoint(pState.getCenterX(), pState.getCenterY());
            var pt = mxUtils.getRotatedPoint(ct, cos, sin, cx);
            state.x = pt.x - state.width / 2;
            state.y = pt.y - state.height / 2;
        }
    }

    this.updateVertexLabelOffset(state);
};

/**
 * Function: updateEdgeState
 *
 * Validates the given cell state.
 */
mxGraphView.prototype.updateEdgeState = function (state, geo) {
    var source = state.getVisibleTerminalState(true);
    var target = state.getVisibleTerminalState(false);

    // This will remove edges with no terminals and no terminal points
    // as such edges are invalid and produce NPEs in the edge styles.
    // Also removes connected edges that have no visible terminals.
    if ((this.graph.model.getTerminal(state.cell, true) != null && source == null) ||
        (source == null && geo.getTerminalPoint(true) == null) ||
        (this.graph.model.getTerminal(state.cell, false) != null && target == null) ||
        (target == null && geo.getTerminalPoint(false) == null)) {
        this.clear(state.cell, true);
    }
    else {
        this.updateFixedTerminalPoints(state, source, target);
        this.updatePoints(state, geo.points, source, target);
        this.updateFloatingTerminalPoints(state, source, target);

        var pts = state.absolutePoints;

        if (state.cell != this.currentRoot && (pts == null || pts.length < 2 ||
            pts[0] == null || pts[pts.length - 1] == null)) {
            // This will remove edges with invalid points from the list of states in the view.
            // Happens if the one of the terminals and the corresponding terminal point is null.
            this.clear(state.cell, true);
        }
        else {
            this.updateEdgeBounds(state);
            this.updateEdgeLabelOffset(state);
        }
    }
};

/**
 * Function: updateVertexLabelOffset
 *
 * Updates the absoluteOffset of the given vertex cell state. This takes
 * into account the label position styles.
 *
 * Parameters:
 *
 * state - <mxCellState> whose absolute offset should be updated.
 */
mxGraphView.prototype.updateVertexLabelOffset = function (state) {
    var h = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);

    if (h == mxConstants.ALIGN_LEFT) {
        var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);

        if (lw != null) {
            lw *= this.scale;
        }
        else {
            lw = state.width;
        }

        state.absoluteOffset.x -= lw;
    }
    else if (h == mxConstants.ALIGN_RIGHT) {
        state.absoluteOffset.x += state.width;
    }
    else if (h == mxConstants.ALIGN_CENTER) {
        var lw = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_WIDTH, null);

        if (lw != null) {
            // Aligns text block with given width inside the vertex width
            var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_CENTER);
            var dx = 0;

            if (align == mxConstants.ALIGN_CENTER) {
                dx = 0.5;
            }
            else if (align == mxConstants.ALIGN_RIGHT) {
                dx = 1;
            }

            if (dx != 0) {
                state.absoluteOffset.x -= (lw * this.scale - state.width) * dx;
            }
        }
    }

    var v = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);

    if (v == mxConstants.ALIGN_TOP) {
        state.absoluteOffset.y -= state.height;
    }
    else if (v == mxConstants.ALIGN_BOTTOM) {
        state.absoluteOffset.y += state.height;
    }
};

/**
 * Function: resetValidationState
 *
 * Resets the current validation state.
 */
mxGraphView.prototype.resetValidationState = function () {
    this.lastNode = null;
    this.lastHtmlNode = null;
    this.lastForegroundNode = null;
    this.lastForegroundHtmlNode = null;
};

/**
 * Function: stateValidated
 *
 * Invoked when a state has been processed in <validatePoints>. This is used
 * to update the order of the DOM nodes of the shape.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the cell state.
 */
mxGraphView.prototype.stateValidated = function (state) {
    var fg = (this.graph.getModel().isEdge(state.cell) && this.graph.keepEdgesInForeground) ||
        (this.graph.getModel().isVertex(state.cell) && this.graph.keepEdgesInBackground);
    var htmlNode = (fg) ? this.lastForegroundHtmlNode || this.lastHtmlNode : this.lastHtmlNode;
    var node = (fg) ? this.lastForegroundNode || this.lastNode : this.lastNode;
    var result = this.graph.cellRenderer.insertStateAfter(state, node, htmlNode);

    if (fg) {
        this.lastForegroundHtmlNode = result[1];
        this.lastForegroundNode = result[0];
    }
    else {
        this.lastHtmlNode = result[1];
        this.lastNode = result[0];
    }
};

/**
 * Function: updateFixedTerminalPoints
 *
 * Sets the initial absolute terminal points in the given state before the edge
 * style is computed.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose initial terminal points should be updated.
 * source - <mxCellState> which represents the source terminal.
 * target - <mxCellState> which represents the target terminal.
 */
mxGraphView.prototype.updateFixedTerminalPoints = function (edge, source, target) {
    this.updateFixedTerminalPoint(edge, source, true,
        this.graph.getConnectionConstraint(edge, source, true));
    this.updateFixedTerminalPoint(edge, target, false,
        this.graph.getConnectionConstraint(edge, target, false));
};

/**
 * Function: updateFixedTerminalPoint
 *
 * Sets the fixed source or target terminal point on the given edge.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose terminal point should be updated.
 * terminal - <mxCellState> which represents the actual terminal.
 * source - Boolean that specifies if the terminal is the source.
 * constraint - <mxConnectionConstraint> that specifies the connection.
 */
mxGraphView.prototype.updateFixedTerminalPoint = function (edge, terminal, source, constraint) {
    edge.setAbsoluteTerminalPoint(this.getFixedTerminalPoint(edge, terminal, source, constraint), source);
};

/**
 * Function: getFixedTerminalPoint
 *
 * Returns the fixed source or target terminal point for the given edge.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose terminal point should be returned.
 * terminal - <mxCellState> which represents the actual terminal.
 * source - Boolean that specifies if the terminal is the source.
 * constraint - <mxConnectionConstraint> that specifies the connection.
 */
mxGraphView.prototype.getFixedTerminalPoint = function (edge, terminal, source, constraint) {
    var pt = null;

    if (constraint != null) {
        pt = this.graph.getConnectionPoint(terminal, constraint, false); // FIXME Rounding introduced bugs when calculating label positions -> , this.graph.isOrthogonal(edge));
    }

    if (pt == null && terminal == null) {
        var s = this.scale;
        var tr = this.translate;
        var orig = edge.origin;
        var geo = this.graph.getCellGeometry(edge.cell);
        pt = geo.getTerminalPoint(source);

        if (pt != null) {
            pt = new mxPoint(s * (tr.x + pt.x + orig.x),
                s * (tr.y + pt.y + orig.y));
        }
    }

    return pt;
};

/**
 * Function: updateBoundsFromStencil
 *
 * Updates the bounds of the given cell state to reflect the bounds of the stencil
 * if it has a fixed aspect and returns the previous bounds as an <mxRectangle> if
 * the bounds have been modified or null otherwise.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose bounds should be updated.
 */
mxGraphView.prototype.updateBoundsFromStencil = function (state) {
    var previous = null;

    if (state != null && state.shape != null && state.shape.stencil != null && state.shape.stencil.aspect == 'fixed') {
        previous = mxRectangle.fromRectangle(state);
        var asp = state.shape.stencil.computeAspect(state.style, state.x, state.y, state.width, state.height);
        state.setRect(asp.x, asp.y, state.shape.stencil.w0 * asp.width, state.shape.stencil.h0 * asp.height);
    }

    return previous;
};

/**
 * Function: updatePoints
 *
 * Updates the absolute points in the given state using the specified array
 * of <mxPoints> as the relative points.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose absolute points should be updated.
 * points - Array of <mxPoints> that constitute the relative points.
 * source - <mxCellState> that represents the source terminal.
 * target - <mxCellState> that represents the target terminal.
 */
mxGraphView.prototype.updatePoints = function (edge, points, source, target) {
    if (edge != null) {
        var pts = [];
        pts.push(edge.absolutePoints[0]);
        var edgeStyle = this.getEdgeStyle(edge, points, source, target);

        if (edgeStyle != null) {
            var src = this.getTerminalPort(edge, source, true);
            var trg = this.getTerminalPort(edge, target, false);

            // Uses the stencil bounds for routing and restores after routing
            var srcBounds = this.updateBoundsFromStencil(src);
            var trgBounds = this.updateBoundsFromStencil(trg);

            edgeStyle(edge, src, trg, points, pts);

            // Restores previous bounds
            if (srcBounds != null) {
                src.setRect(srcBounds.x, srcBounds.y, srcBounds.width, srcBounds.height);
            }

            if (trgBounds != null) {
                trg.setRect(trgBounds.x, trgBounds.y, trgBounds.width, trgBounds.height);
            }
        }
        else if (points != null) {
            for (var i = 0; i < points.length; i++) {
                if (points[i] != null) {
                    var pt = mxUtils.clone(points[i]);
                    pts.push(this.transformControlPoint(edge, pt));
                }
            }
        }

        var tmp = edge.absolutePoints;
        pts.push(tmp[tmp.length - 1]);

        edge.absolutePoints = pts;
    }
};

/**
 * Function: transformControlPoint
 *
 * Transforms the given control point to an absolute point.
 */
mxGraphView.prototype.transformControlPoint = function (state, pt, ignoreScale) {
    if (state != null && pt != null) {
        var orig = state.origin;
        var scale = ignoreScale ? 1 : this.scale

        return new mxPoint(scale * (pt.x + this.translate.x + orig.x),
            scale * (pt.y + this.translate.y + orig.y));
    }

    return null;
};

/**
 * Function: isLoopStyleEnabled
 *
 * Returns true if the given edge should be routed with <mxGraph.defaultLoopStyle>
 * or the <mxConstants.STYLE_LOOP> defined for the given edge. This implementation
 * returns true if the given edge is a loop and does not have connections constraints
 * associated.
 */
mxGraphView.prototype.isLoopStyleEnabled = function (edge, points, source, target) {
    var sc = this.graph.getConnectionConstraint(edge, source, true);
    var tc = this.graph.getConnectionConstraint(edge, target, false);

    if ((points == null || points.length < 2) &&
        (!mxUtils.getValue(edge.style, mxConstants.STYLE_ORTHOGONAL_LOOP, false) ||
            ((sc == null || sc.point == null) && (tc == null || tc.point == null)))) {
        return source != null && source == target;
    }

    return false;
};

/**
 * Function: getEdgeStyle
 *
 * Returns the edge style function to be used to render the given edge state.
 */
mxGraphView.prototype.getEdgeStyle = function (edge, points, source, target) {
    var edgeStyle = this.isLoopStyleEnabled(edge, points, source, target) ?
        mxUtils.getValue(edge.style, mxConstants.STYLE_LOOP, this.graph.defaultLoopStyle) :
        (!mxUtils.getValue(edge.style, mxConstants.STYLE_NOEDGESTYLE, false) ?
            edge.style[mxConstants.STYLE_EDGE] : null);

    // Converts string values to objects
    if (typeof (edgeStyle) == "string") {
        var tmp = mxStyleRegistry.getValue(edgeStyle);

        if (tmp == null && this.isAllowEval()) {
            tmp = mxUtils.eval(edgeStyle);
        }

        edgeStyle = tmp;
    }

    if (typeof (edgeStyle) == "function") {
        return edgeStyle;
    }

    return null;
};

/**
 * Function: updateFloatingTerminalPoints
 *
 * Updates the terminal points in the given state after the edge style was
 * computed for the edge.
 *
 * Parameters:
 *
 * state - <mxCellState> whose terminal points should be updated.
 * source - <mxCellState> that represents the source terminal.
 * target - <mxCellState> that represents the target terminal.
 */
mxGraphView.prototype.updateFloatingTerminalPoints = function (state, source, target) {
    var pts = state.absolutePoints;
    var p0 = pts[0];
    var pe = pts[pts.length - 1];

    if (pe == null && target != null) {
        this.updateFloatingTerminalPoint(state, target, source, false);
    }

    if (p0 == null && source != null) {
        this.updateFloatingTerminalPoint(state, source, target, true);
    }
};

/**
 * Function: updateFloatingTerminalPoint
 *
 * Updates the absolute terminal point in the given state for the given
 * start and end state, where start is the source if source is true.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose terminal point should be updated.
 * start - <mxCellState> for the terminal on "this" side of the edge.
 * end - <mxCellState> for the terminal on the other side of the edge.
 * source - Boolean indicating if start is the source terminal state.
 */
mxGraphView.prototype.updateFloatingTerminalPoint = function (edge, start, end, source) {
    edge.setAbsoluteTerminalPoint(this.getFloatingTerminalPoint(edge, start, end, source), source);
};

/**
 * Function: getFloatingTerminalPoint
 *
 * Returns the floating terminal point for the given edge, start and end
 * state, where start is the source if source is true.
 *
 * Parameters:
 *
 * edge - <mxCellState> whose terminal point should be returned.
 * start - <mxCellState> for the terminal on "this" side of the edge.
 * end - <mxCellState> for the terminal on the other side of the edge.
 * source - Boolean indicating if start is the source terminal state.
 */
mxGraphView.prototype.getFloatingTerminalPoint = function (edge, start, end, source) {
    start = this.getTerminalPort(edge, start, source);
    var next = this.getNextPoint(edge, end, source);

    var orth = this.graph.isOrthogonal(edge);
    var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0'));
    var center = new mxPoint(start.getCenterX(), start.getCenterY());

    if (alpha != 0) {
        var cos = Math.cos(-alpha);
        var sin = Math.sin(-alpha);
        next = mxUtils.getRotatedPoint(next, cos, sin, center);
    }

    var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
    border += parseFloat(edge.style[(source) ?
        mxConstants.STYLE_SOURCE_PERIMETER_SPACING :
        mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0);
    var pt = this.getPerimeterPoint(start, next, alpha == 0 && orth, border);

    if (alpha != 0) {
        var cos = Math.cos(alpha);
        var sin = Math.sin(alpha);
        pt = mxUtils.getRotatedPoint(pt, cos, sin, center);
    }

    return pt;
};

/**
 * Function: getTerminalPort
 *
 * Returns an <mxCellState> that represents the source or target terminal or
 * port for the given edge.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the state of the edge.
 * terminal - <mxCellState> that represents the terminal.
 * source - Boolean indicating if the given terminal is the source terminal.
 */
mxGraphView.prototype.getTerminalPort = function (state, terminal, source) {
    var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
        mxConstants.STYLE_TARGET_PORT;
    var id = mxUtils.getValue(state.style, key);

    if (id != null) {
        var tmp = this.getState(this.graph.getModel().getCell(id));

        // Only uses ports where a cell state exists
        if (tmp != null) {
            terminal = tmp;
        }
    }

    return terminal;
};

/**
 * Function: getPerimeterPoint
 *
 * Returns an <mxPoint> that defines the location of the intersection point between
 * the perimeter and the line between the center of the shape and the given point.
 *
 * Parameters:
 *
 * terminal - <mxCellState> for the source or target terminal.
 * next - <mxPoint> that lies outside of the given terminal.
 * orthogonal - Boolean that specifies if the orthogonal projection onto
 * the perimeter should be returned. If this is false then the intersection
 * of the perimeter and the line between the next and the center point is
 * returned.
 * border - Optional border between the perimeter and the shape.
 */
mxGraphView.prototype.getPerimeterPoint = function (terminal, next, orthogonal, border) {
    var point = null;

    if (terminal != null) {
        var perimeter = this.getPerimeterFunction(terminal);

        if (perimeter != null && next != null) {
            var bounds = this.getPerimeterBounds(terminal, border);

            if (bounds.width > 0 || bounds.height > 0) {
                point = new mxPoint(next.x, next.y);
                var flipH = false;
                var flipV = false;

                if (this.graph.model.isVertex(terminal.cell)) {
                    flipH = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPH, 0) == 1;
                    flipV = mxUtils.getValue(terminal.style, mxConstants.STYLE_FLIPV, 0) == 1;

                    // Legacy support for stencilFlipH/V
                    if (terminal.shape != null && terminal.shape.stencil != null) {
                        flipH = (mxUtils.getValue(terminal.style, 'stencilFlipH', 0) == 1) || flipH;
                        flipV = (mxUtils.getValue(terminal.style, 'stencilFlipV', 0) == 1) || flipV;
                    }

                    if (flipH) {
                        point.x = 2 * bounds.getCenterX() - point.x;
                    }

                    if (flipV) {
                        point.y = 2 * bounds.getCenterY() - point.y;
                    }
                }

                point = perimeter(bounds, terminal, point, orthogonal);

                if (point != null) {
                    if (flipH) {
                        point.x = 2 * bounds.getCenterX() - point.x;
                    }

                    if (flipV) {
                        point.y = 2 * bounds.getCenterY() - point.y;
                    }
                }
            }
        }

        if (point == null) {
            point = this.getPoint(terminal);
        }
    }

    return point;
};

/**
 * Function: getRoutingCenterX
 *
 * Returns the x-coordinate of the center point for automatic routing.
 */
mxGraphView.prototype.getRoutingCenterX = function (state) {
    var f = (state.style != null) ? parseFloat(state.style
    [mxConstants.STYLE_ROUTING_CENTER_X]) || 0 : 0;

    return state.getCenterX() + f * state.width;
};

/**
 * Function: getRoutingCenterY
 *
 * Returns the y-coordinate of the center point for automatic routing.
 */
mxGraphView.prototype.getRoutingCenterY = function (state) {
    var f = (state.style != null) ? parseFloat(state.style
    [mxConstants.STYLE_ROUTING_CENTER_Y]) || 0 : 0;

    return state.getCenterY() + f * state.height;
};

/**
 * Function: getPerimeterBounds
 *
 * Returns the perimeter bounds for the given terminal, edge pair as an
 * <mxRectangle>.
 *
 * If you have a model where each terminal has a relative child that should
 * act as the graphical endpoint for a connection from/to the terminal, then
 * this method can be replaced as follows:
 *
 * (code)
 * var oldGetPerimeterBounds = mxGraphView.prototype.getPerimeterBounds;
 * mxGraphView.prototype.getPerimeterBounds = function(terminal, edge, isSource)
 * {
 *   var model = this.graph.getModel();
 *   var childCount = model.getChildCount(terminal.cell);
 *
 *   if (childCount > 0)
 *   {
 *     var child = model.getChildAt(terminal.cell, 0);
 *     var geo = model.getGeometry(child);
 *
 *     if (geo != null &&
 *         geo.relative)
 *     {
 *       var state = this.getState(child);
 *
 *       if (state != null)
 *       {
 *         terminal = state;
 *       }
 *     }
 *   }
 *
 *   return oldGetPerimeterBounds.apply(this, arguments);
 * };
 * (end)
 *
 * Parameters:
 *
 * terminal - <mxCellState> that represents the terminal.
 * border - Number that adds a border between the shape and the perimeter.
 */
mxGraphView.prototype.getPerimeterBounds = function (terminal, border) {
    border = (border != null) ? border : 0;

    if (terminal != null) {
        border += parseFloat(terminal.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
    }

    return terminal.getPerimeterBounds(border * this.scale);
};

/**
 * Function: getPerimeterFunction
 *
 * Returns the perimeter function for the given state.
 */
mxGraphView.prototype.getPerimeterFunction = function (state) {
    var perimeter = state.style[mxConstants.STYLE_PERIMETER];

    // Converts string values to objects
    if (typeof (perimeter) == "string") {
        var tmp = mxStyleRegistry.getValue(perimeter);

        if (tmp == null && this.isAllowEval()) {
            tmp = mxUtils.eval(perimeter);
        }

        perimeter = tmp;
    }

    if (typeof (perimeter) == "function") {
        return perimeter;
    }

    return null;
};

/**
 * Function: getNextPoint
 *
 * Returns the nearest point in the list of absolute points or the center
 * of the opposite terminal.
 *
 * Parameters:
 *
 * edge - <mxCellState> that represents the edge.
 * opposite - <mxCellState> that represents the opposite terminal.
 * source - Boolean indicating if the next point for the source or target
 * should be returned.
 */
mxGraphView.prototype.getNextPoint = function (edge, opposite, source) {
    var pts = edge.absolutePoints;
    var point = null;

    if (pts != null && pts.length >= 2) {
        var count = pts.length;
        point = pts[(source) ? Math.min(1, count - 1) : Math.max(0, count - 2)];
    }

    if (point == null && opposite != null) {
        point = new mxPoint(opposite.getCenterX(), opposite.getCenterY());
    }

    return point;
};

/**
 * Function: getVisibleTerminal
 *
 * Returns the nearest ancestor terminal that is visible. The edge appears
 * to be connected to this terminal on the display. The result of this method
 * is cached in <mxCellState.getVisibleTerminalState>.
 *
 * Parameters:
 *
 * edge - <mxCell> whose visible terminal should be returned.
 * source - Boolean that specifies if the source or target terminal
 * should be returned.
 */
mxGraphView.prototype.getVisibleTerminal = function (edge, source) {
    var model = this.graph.getModel();
    var result = model.getTerminal(edge, source);
    var best = result;

    while (result != null && result != this.currentRoot) {
        if (!this.graph.isCellVisible(best) || this.isCellCollapsed(result)) {
            best = result;
        }

        result = model.getParent(result);
    }

    // Checks if the result is valid for the current view state
    if (best != null && (!model.contains(best) ||
        model.getParent(best) == model.getRoot() ||
        best == this.currentRoot)) {
        best = null;
    }

    return best;
};

/**
 * Function: updateEdgeBounds
 *
 * Updates the given state using the bounding box of t
 * he absolute points.
 * Also updates <mxCellState.terminalDistance>, <mxCellState.length> and
 * <mxCellState.segments>.
 *
 * Parameters:
 *
 * state - <mxCellState> whose bounds should be updated.
 */
mxGraphView.prototype.updateEdgeBounds = function (state) {
    var points = state.absolutePoints;
    var p0 = points[0];
    var pe = points[points.length - 1];

    if (p0.x != pe.x || p0.y != pe.y) {
        var dx = pe.x - p0.x;
        var dy = pe.y - p0.y;
        state.terminalDistance = Math.sqrt(dx * dx + dy * dy);
    }
    else {
        state.terminalDistance = 0;
    }

    var length = 0;
    var segments = [];
    var pt = p0;

    if (pt != null) {
        var minX = pt.x;
        var minY = pt.y;
        var maxX = minX;
        var maxY = minY;

        for (var i = 1; i < points.length; i++) {
            var tmp = points[i];

            if (tmp != null) {
                var dx = pt.x - tmp.x;
                var dy = pt.y - tmp.y;

                var segment = Math.sqrt(dx * dx + dy * dy);
                segments.push(segment);
                length += segment;

                pt = tmp;

                minX = Math.min(pt.x, minX);
                minY = Math.min(pt.y, minY);
                maxX = Math.max(pt.x, maxX);
                maxY = Math.max(pt.y, maxY);
            }
        }

        state.length = length;
        state.segments = segments;

        var markerSize = 1; // TODO: include marker size

        state.x = minX;
        state.y = minY;
        state.width = Math.max(markerSize, maxX - minX);
        state.height = Math.max(markerSize, maxY - minY);
    }
};

/**
 * Function: getPoint
 *
 * Returns the absolute point on the edge for the given relative
 * <mxGeometry> as an <mxPoint>. The edge is represented by the given
 * <mxCellState>.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the state of the parent edge.
 * geometry - <mxGeometry> that represents the relative location.
 */
mxGraphView.prototype.getPoint = function (state, geometry) {
    var x = state.getCenterX();
    var y = state.getCenterY();

    if (state.segments != null && (geometry == null || geometry.relative)) {
        var gx = (geometry != null) ? geometry.x / 2 : 0;
        var pointCount = state.absolutePoints.length;
        var dist = Math.round((gx + 0.5) * state.length);
        var segment = state.segments[0];
        var length = 0;
        var index = 1;

        while (dist >= Math.round(length + segment) && index < pointCount - 1) {
            length += segment;
            segment = state.segments[index++];
        }

        var factor = (segment == 0) ? 0 : (dist - length) / segment;
        var p0 = state.absolutePoints[index - 1];
        var pe = state.absolutePoints[index];

        if (p0 != null && pe != null) {
            var gy = 0;
            var offsetX = 0;
            var offsetY = 0;

            if (geometry != null) {
                gy = geometry.y;
                var offset = geometry.offset;

                if (offset != null) {
                    offsetX = offset.x;
                    offsetY = offset.y;
                }
            }

            var dx = pe.x - p0.x;
            var dy = pe.y - p0.y;
            var nx = (segment == 0) ? 0 : dy / segment;
            var ny = (segment == 0) ? 0 : dx / segment;

            x = p0.x + dx * factor + (nx * gy + offsetX) * this.scale;
            y = p0.y + dy * factor - (ny * gy - offsetY) * this.scale;
        }
    }
    else if (geometry != null) {
        var offset = geometry.offset;

        if (offset != null) {
            x += offset.x;
            y += offset.y;
        }
    }

    return new mxPoint(x, y);
};

/**
 * Function: getRelativePoint
 *
 * Gets the relative point that describes the given, absolute label
 * position for the given edge state.
 *
 * Parameters:
 *
 * state - <mxCellState> that represents the state of the parent edge.
 * x - Specifies the x-coordinate of the absolute label location.
 * y - Specifies the y-coordinate of the absolute label location.
 */
mxGraphView.prototype.getRelativePoint = function (edgeState, x, y) {
    var model = this.graph.getModel();
    var geometry = model.getGeometry(edgeState.cell);

    if (geometry != null) {
        var pointCount = edgeState.absolutePoints.length;

        if (geometry.relative && pointCount > 1) {
            var totalLength = edgeState.length;
            var segments = edgeState.segments;

            // Works which line segment the point of the label is closest to
            var p0 = edgeState.absolutePoints[0];
            var pe = edgeState.absolutePoints[1];
            var minDist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);

            var index = 0;
            var tmp = 0;
            var length = 0;

            for (var i = 2; i < pointCount; i++) {
                tmp += segments[i - 2];
                pe = edgeState.absolutePoints[i];
                var dist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);

                if (dist <= minDist) {
                    minDist = dist;
                    index = i - 1;
                    length = tmp;
                }

                p0 = pe;
            }

            var seg = segments[index];
            p0 = edgeState.absolutePoints[index];
            pe = edgeState.absolutePoints[index + 1];

            var x2 = p0.x;
            var y2 = p0.y;

            var x1 = pe.x;
            var y1 = pe.y;

            var px = x;
            var py = y;

            var xSegment = x2 - x1;
            var ySegment = y2 - y1;

            px -= x1;
            py -= y1;
            var projlenSq = 0;

            px = xSegment - px;
            py = ySegment - py;
            var dotprod = px * xSegment + py * ySegment;

            if (dotprod <= 0.0) {
                projlenSq = 0;
            }
            else {
                projlenSq = dotprod * dotprod
                    / (xSegment * xSegment + ySegment * ySegment);
            }

            var projlen = Math.sqrt(projlenSq);

            if (projlen > seg) {
                projlen = seg;
            }

            var yDistance = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, pe
                .x, pe.y, x, y));
            var direction = mxUtils.relativeCcw(p0.x, p0.y, pe.x, pe.y, x, y);

            if (direction == -1) {
                yDistance = -yDistance;
            }

            // Constructs the relative point for the label
            return new mxPoint(((totalLength / 2 - length - projlen) / totalLength) * -2,
                yDistance / this.scale);
        }
    }

    return new mxPoint();
};

/**
 * Function: updateEdgeLabelOffset
 *
 * Updates <mxCellState.absoluteOffset> for the given state. The absolute
 * offset is normally used for the position of the edge label. Is is
 * calculated from the geometry as an absolute offset from the center
 * between the two endpoints if the geometry is absolute, or as the
 * relative distance between the center along the line and the absolute
 * orthogonal distance if the geometry is relative.
 *
 * Parameters:
 *
 * state - <mxCellState> whose absolute offset should be updated.
 */
mxGraphView.prototype.updateEdgeLabelOffset = function (state) {
    var points = state.absolutePoints;

    state.absoluteOffset.x = state.getCenterX();
    state.absoluteOffset.y = state.getCenterY();

    if (points != null && points.length > 0 && state.segments != null) {
        var geometry = this.graph.getCellGeometry(state.cell);

        if (geometry.relative) {
            var offset = this.getPoint(state, geometry);

            if (offset != null) {
                state.absoluteOffset = offset;
            }
        }
        else {
            var p0 = points[0];
            var pe = points[points.length - 1];

            if (p0 != null && pe != null) {
                var dx = pe.x - p0.x;
                var dy = pe.y - p0.y;
                var x0 = 0;
                var y0 = 0;

                var off = geometry.offset;

                if (off != null) {
                    x0 = off.x;
                    y0 = off.y;
                }

                var x = p0.x + dx / 2 + x0 * this.scale;
                var y = p0.y + dy / 2 + y0 * this.scale;

                state.absoluteOffset.x = x;
                state.absoluteOffset.y = y;
            }
        }
    }
};

/**
 * Function: getState
 *
 * Returns the <mxCellState> for the given cell. If create is true, then
 * the state is created if it does not yet exist.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the <mxCellState> should be returned.
 * create - Optional boolean indicating if a new state should be created
 * if it does not yet exist. Default is false.
 */
mxGraphView.prototype.getState = function (cell, create) {
    create = create || false;
    var state = null;

    if (cell != null) {
        state = this.states.get(cell);

        if (create && (state == null || this.updateStyle) && this.graph.isCellVisible(cell)) {
            if (state == null) {
                state = this.createState(cell);
                this.states.put(cell, state);
            }
            else {
                state.style = this.graph.getCellStyle(cell);
            }
        }
    }

    return state;
};

/**
 * Function: isRendering
 *
 * Returns <rendering>.
 */
mxGraphView.prototype.isRendering = function () {
    return this.rendering;
};

/**
 * Function: setRendering
 *
 * Sets <rendering>.
 */
mxGraphView.prototype.setRendering = function (value) {
    this.rendering = value;
};

/**
 * Function: isAllowEval
 *
 * Returns <allowEval>.
 */
mxGraphView.prototype.isAllowEval = function () {
    return this.allowEval;
};

/**
 * Function: setAllowEval
 *
 * Sets <allowEval>.
 */
mxGraphView.prototype.setAllowEval = function (value) {
    this.allowEval = value;
};

/**
 * Function: getStates
 *
 * Returns <states>.
 */
mxGraphView.prototype.getStates = function () {
    return this.states;
};

/**
 * Function: setStates
 *
 * Sets <states>.
 */
mxGraphView.prototype.setStates = function (value) {
    this.states = value;
};

/**
 * Function: getCellStates
 *
 * Returns the <mxCellStates> for the given array of <mxCells>. The array
 * contains all states that are not null, that is, the returned array may
 * have less elements than the given array. If no argument is given, then
 * this returns <states>.
 */
mxGraphView.prototype.getCellStates = function (cells) {
    if (cells == null) {
        return this.states;
    }
    else {
        var result = [];

        for (var i = 0; i < cells.length; i++) {
            var state = this.getState(cells[i]);

            if (state != null) {
                result.push(state);
            }
        }

        return result;
    }
};

/**
 * Function: removeState
 *
 * Removes and returns the <mxCellState> for the given cell.
 *
 * Parameters:
 *
 * cell - <mxCell> for which the <mxCellState> should be removed.
 */
mxGraphView.prototype.removeState = function (cell) {
    var state = null;

    if (cell != null) {
        state = this.states.remove(cell);

        if (state != null) {
            this.graph.cellRenderer.destroy(state);
            state.invalid = true;
            state.destroy();
        }
    }

    return state;
};

/**
 * Function: createState
 *
 * Creates and returns an <mxCellState> for the given cell and initializes
 * it using <mxCellRenderer.initialize>.
 *
 * Parameters:
 *
 * cell - <mxCell> for which a new <mxCellState> should be created.
 */
mxGraphView.prototype.createState = function (cell) {
    return new mxCellState(this, cell, this.graph.getCellStyle(cell));
};

/**
 * Function: getCanvas
 *
 * Returns the DOM node that contains the background-, draw- and
 * overlay- and decoratorpanes.
 */
mxGraphView.prototype.getCanvas = function () {
    return this.canvas;
};

/**
 * Function: getBackgroundPane
 *
 * Returns the DOM node that represents the background layer.
 */
mxGraphView.prototype.getBackgroundPane = function () {
    return this.backgroundPane;
};

/**
 * Function: getDrawPane
 *
 * Returns the DOM node that represents the main drawing layer.
 */
mxGraphView.prototype.getDrawPane = function () {
    return this.drawPane;
};

/**
 * Function: getOverlayPane
 *
 * Returns the DOM node that represents the layer above the drawing layer.
 */
mxGraphView.prototype.getOverlayPane = function () {
    return this.overlayPane;
};

/**
 * Function: getDecoratorPane
 *
 * Returns the DOM node that represents the topmost drawing layer.
 */
mxGraphView.prototype.getDecoratorPane = function () {
    return this.decoratorPane;
};

/**
 * Function: isContainerEvent
 *
 * Returns true if the event origin is one of the drawing panes or
 * containers of the view.
 */
mxGraphView.prototype.isContainerEvent = function (evt) {
    var source = mxEvent.getSource(evt);

    return (source == this.graph.container ||
        source.parentNode == this.backgroundPane ||
        (source.parentNode != null &&
            source.parentNode.parentNode == this.backgroundPane) ||
        source == this.canvas.parentNode ||
        source == this.canvas ||
        source == this.backgroundPane ||
        source == this.drawPane ||
        source == this.overlayPane ||
        source == this.decoratorPane);
};

/**
 * Function: isScrollEvent
 *
 * Returns true if the event origin is one of the scrollbars of the
 * container in IE. Such events are ignored.
 */
mxGraphView.prototype.isScrollEvent = function (evt) {
    var offset = mxUtils.getOffset(this.graph.container);
    var pt = new mxPoint(evt.clientX - offset.x, evt.clientY - offset.y);

    var outWidth = this.graph.container.offsetWidth;
    var inWidth = this.graph.container.clientWidth;

    if (outWidth > inWidth && pt.x > inWidth + 2 && pt.x <= outWidth) {
        return true;
    }

    var outHeight = this.graph.container.offsetHeight;
    var inHeight = this.graph.container.clientHeight;

    if (outHeight > inHeight && pt.y > inHeight + 2 && pt.y <= outHeight) {
        return true;
    }

    return false;
};

/**
 * Function: init
 *
 * Initializes the graph event dispatch loop for the specified container
 * and invokes <create> to create the required DOM nodes for the display.
 */
mxGraphView.prototype.init = function () {
    this.installListeners();

    // Creates the DOM nodes for the respective display dialect
    var graph = this.graph;

    if (graph.dialect == mxConstants.DIALECT_SVG) {
        this.createSvg();
    }
    else if (graph.dialect == mxConstants.DIALECT_VML) {
        this.createVml();
    }
    else {
        this.createHtml();
    }
};

/**
 * Function: installListeners
 *
 * Installs the required listeners in the container.
 */
mxGraphView.prototype.installListeners = function () {
    var graph = this.graph;
    var container = graph.container;

    if (container != null) {
        // Support for touch device gestures (eg. pinch to zoom)
        // Double-tap handling is implemented in mxGraph.fireMouseEvent
        if (mxClient.IS_TOUCH) {
            mxEvent.addListener(container, 'gesturestart', mxUtils.bind(this, function (evt) {
                graph.fireGestureEvent(evt);
                mxEvent.consume(evt);
            }));

            mxEvent.addListener(container, 'gesturechange', mxUtils.bind(this, function (evt) {
                graph.fireGestureEvent(evt);
                mxEvent.consume(evt);
            }));

            mxEvent.addListener(container, 'gestureend', mxUtils.bind(this, function (evt) {
                graph.fireGestureEvent(evt);
                mxEvent.consume(evt);
            }));
        }

        // Fires event only for one pointer per gesture
        var pointerId = null;

        // Adds basic listeners for graph event dispatching
        mxEvent.addGestureListeners(container, mxUtils.bind(this, function (evt) {
            // Condition to avoid scrollbar events starting a rubberband selection
            if (this.isContainerEvent(evt) && ((!mxClient.IS_IE && !mxClient.IS_IE11 && !mxClient.IS_GC &&
                !mxClient.IS_OP && !mxClient.IS_SF) || !this.isScrollEvent(evt))) {
                graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
                pointerId = evt.pointerId;
            }
        }),
            mxUtils.bind(this, function (evt) {
                if (this.isContainerEvent(evt) && (pointerId == null || evt.pointerId == pointerId)) {
                    graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt));
                }
            }),
            mxUtils.bind(this, function (evt) {
                if (this.isContainerEvent(evt)) {
                    graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
                }

                pointerId = null;
            }));

        // Adds listener for double click handling on background, this does always
        // use native event handler, we assume that the DOM of the background
        // does not change during the double click
        mxEvent.addListener(container, 'dblclick', mxUtils.bind(this, function (evt) {
            if (this.isContainerEvent(evt)) {
                graph.dblClick(evt);
            }
        }));

        // Workaround for touch events which started on some DOM node
        // on top of the container, in which case the cells under the
        // mouse for the move and up events are not detected.
        var getState = function (evt) {
            var state = null;

            // Workaround for touch events which started on some DOM node
            // on top of the container, in which case the cells under the
            // mouse for the move and up events are not detected.
            if (mxClient.IS_TOUCH) {
                var x = mxEvent.getClientX(evt);
                var y = mxEvent.getClientY(evt);

                // Dispatches the drop event to the graph which
                // consumes and executes the source function
                var pt = mxUtils.convertPoint(container, x, y);
                state = graph.view.getState(graph.getCellAt(pt.x, pt.y));
            }

            return state;
        };

        // Adds basic listeners for graph event dispatching outside of the
        // container and finishing the handling of a single gesture
        // Implemented via graph event dispatch loop to avoid duplicate events
        // in Firefox and Chrome
        graph.addMouseListener(
            {
                mouseDown: function (sender, me) {
                    graph.popupMenuHandler.hideMenu();
                },
                mouseMove: function () { },
                mouseUp: function () { }
            });

        this.moveHandler = mxUtils.bind(this, function (evt) {
            // Hides the tooltip if mouse is outside container
            if (graph.tooltipHandler != null && graph.tooltipHandler.isHideOnHover()) {
                graph.tooltipHandler.hide();
            }

            if (this.captureDocumentGesture && graph.isMouseDown && graph.container != null &&
                !this.isContainerEvent(evt) && graph.container.style.display != 'none' &&
                graph.container.style.visibility != 'hidden' && !mxEvent.isConsumed(evt)) {
                graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, getState(evt)));
            }
        });

        this.endHandler = mxUtils.bind(this, function (evt) {
            if (this.captureDocumentGesture && graph.isMouseDown && graph.container != null &&
                !this.isContainerEvent(evt) && graph.container.style.display != 'none' &&
                graph.container.style.visibility != 'hidden') {
                graph.fireMouseEvent(mxEvent.MOUSE_UP, new mxMouseEvent(evt));
            }
        });

        mxEvent.addGestureListeners(document, null, this.moveHandler, this.endHandler);
    }
};

/**
 * Function: createHtml
 *
 * Creates the DOM nodes for the HTML display.
 */
mxGraphView.prototype.createHtml = function () {
    var container = this.graph.container;

    if (container != null) {
        this.canvas = this.createHtmlPane('100%', '100%');
        this.canvas.style.overflow = 'hidden';

        // Uses minimal size for inner DIVs on Canvas. This is required
        // for correct event processing in IE. If we have an overlapping
        // DIV then the events on the cells are only fired for labels.
        this.backgroundPane = this.createHtmlPane('1px', '1px');
        this.drawPane = this.createHtmlPane('1px', '1px');
        this.overlayPane = this.createHtmlPane('1px', '1px');
        this.decoratorPane = this.createHtmlPane('1px', '1px');

        this.canvas.appendChild(this.backgroundPane);
        this.canvas.appendChild(this.drawPane);
        this.canvas.appendChild(this.overlayPane);
        this.canvas.appendChild(this.decoratorPane);

        container.appendChild(this.canvas);
        this.updateContainerStyle(container);

        // Implements minWidth/minHeight in quirks mode
        if (mxClient.IS_QUIRKS) {
            var onResize = mxUtils.bind(this, function (evt) {
                var bounds = this.getGraphBounds();
                var width = bounds.x + bounds.width + this.graph.border;
                var height = bounds.y + bounds.height + this.graph.border;

                this.updateHtmlCanvasSize(width, height);
            });

            mxEvent.addListener(window, 'resize', onResize);
        }
    }
};

/**
 * Function: updateHtmlCanvasSize
 *
 * Updates the size of the HTML canvas.
 */
mxGraphView.prototype.updateHtmlCanvasSize = function (width, height) {
    if (this.graph.container != null) {
        var ow = this.graph.container.offsetWidth;
        var oh = this.graph.container.offsetHeight;

        if (ow < width) {
            this.canvas.style.width = width + 'px';
        }
        else {
            this.canvas.style.width = '100%';
        }

        if (oh < height) {
            this.canvas.style.height = height + 'px';
        }
        else {
            this.canvas.style.height = '100%';
        }
    }
};

/**
 * Function: createHtmlPane
 *
 * Creates and returns a drawing pane in HTML (DIV).
 */
mxGraphView.prototype.createHtmlPane = function (width, height) {
    var pane = document.createElement('DIV');

    if (width != null && height != null) {
        pane.style.position = 'absolute';
        pane.style.left = '0px';
        pane.style.top = '0px';

        pane.style.width = width;
        pane.style.height = height;
    }
    else {
        pane.style.position = 'relative';
    }

    return pane;
};

/**
 * Function: createVml
 *
 * Creates the DOM nodes for the VML display.
 */
mxGraphView.prototype.createVml = function () {
    var container = this.graph.container;

    if (container != null) {
        var width = container.offsetWidth;
        var height = container.offsetHeight;
        this.canvas = this.createVmlPane(width, height);
        this.canvas.style.overflow = 'hidden';

        this.backgroundPane = this.createVmlPane(width, height);
        this.drawPane = this.createVmlPane(width, height);
        this.overlayPane = this.createVmlPane(width, height);
        this.decoratorPane = this.createVmlPane(width, height);

        this.canvas.appendChild(this.backgroundPane);
        this.canvas.appendChild(this.drawPane);
        this.canvas.appendChild(this.overlayPane);
        this.canvas.appendChild(this.decoratorPane);

        container.appendChild(this.canvas);
    }
};

/**
 * Function: createVmlPane
 *
 * Creates a drawing pane in VML (group).
 */
mxGraphView.prototype.createVmlPane = function (width, height) {
    var pane = document.createElement(mxClient.VML_PREFIX + ':group');

    // At this point the width and height are potentially
    // uninitialized. That's OK.
    pane.style.position = 'absolute';
    pane.style.left = '0px';
    pane.style.top = '0px';

    pane.style.width = width + 'px';
    pane.style.height = height + 'px';

    pane.setAttribute('coordsize', width + ',' + height);
    pane.setAttribute('coordorigin', '0,0');

    return pane;
};

/**
 * Function: createSvg
 *
 * Creates and returns the DOM nodes for the SVG display.
 */
mxGraphView.prototype.createSvg = function () {
    var container = this.graph.container;
    this.canvas = document.createElementNS(mxConstants.NS_SVG, 'g');

    // For background image
    this.backgroundPane = document.createElementNS(mxConstants.NS_SVG, 'g');
    this.canvas.appendChild(this.backgroundPane);

    // Adds two layers (background is early feature)
    this.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
    this.canvas.appendChild(this.drawPane);

    this.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
    this.canvas.appendChild(this.overlayPane);

    this.decoratorPane = document.createElementNS(mxConstants.NS_SVG, 'g');
    this.canvas.appendChild(this.decoratorPane);

    var root = document.createElementNS(mxConstants.NS_SVG, 'svg');
    root.style.left = '0px';
    root.style.top = '0px';
    root.style.width = '100%';
    root.style.height = '100%';

    // NOTE: In standards mode, the SVG must have block layout
    // in order for the container DIV to not show scrollbars.
    root.style.display = 'block';
    root.appendChild(this.canvas);

    // Workaround for scrollbars in IE11 and below
    if (mxClient.IS_IE || mxClient.IS_IE11) {
        root.style.overflow = 'hidden';
    }

    if (container != null) {
        container.appendChild(root);
        this.updateContainerStyle(container);
    }
};

/**
 * Function: updateContainerStyle
 *
 * Updates the style of the container after installing the SVG DOM elements.
 */
mxGraphView.prototype.updateContainerStyle = function (container) {
    // Workaround for offset of container
    var style = mxUtils.getCurrentStyle(container);

    if (style != null && style.position == 'static') {
        container.style.position = 'relative';
    }

    // Disables built-in pan and zoom in IE10 and later
    if (mxClient.IS_POINTER) {
        container.style.touchAction = 'none';
    }
};

/**
 * Function: destroy
 *
 * Destroys the view and all its resources.
 */
mxGraphView.prototype.destroy = function () {
    var root = (this.canvas != null) ? this.canvas.ownerSVGElement : null;

    if (root == null) {
        root = this.canvas;
    }

    if (root != null && root.parentNode != null) {
        this.clear(this.currentRoot, true);
        mxEvent.removeGestureListeners(document, null, this.moveHandler, this.endHandler);
        mxEvent.release(this.graph.container);
        root.parentNode.removeChild(root);

        this.moveHandler = null;
        this.endHandler = null;
        this.canvas = null;
        this.backgroundPane = null;
        this.drawPane = null;
        this.overlayPane = null;
        this.decoratorPane = null;
    }
};

/**
 * Class: mxCurrentRootChange
 *
 * Action to change the current root in a view.
 *
 * Constructor: mxCurrentRootChange
 *
 * Constructs a change of the current root in the given view.
 */
function mxCurrentRootChange(view, root) {
    this.view = view;
    this.root = root;
    this.previous = root;
    this.isUp = root == null;

    if (!this.isUp) {
        var tmp = this.view.currentRoot;
        var model = this.view.graph.getModel();

        while (tmp != null) {
            if (tmp == root) {
                this.isUp = true;
                break;
            }

            tmp = model.getParent(tmp);
        }
    }
};

/**
 * Function: execute
 *
 * Changes the current root of the view.
 */
mxCurrentRootChange.prototype.execute = function () {
    var tmp = this.view.currentRoot;
    this.view.currentRoot = this.previous;
    this.previous = tmp;

    var translate = this.view.graph.getTranslateForRoot(this.view.currentRoot);

    if (translate != null) {
        this.view.translate = new mxPoint(-translate.x, -translate.y);
    }

    if (this.isUp) {
        this.view.clear(this.view.currentRoot, true);
        this.view.validate();
    }
    else {
        this.view.refresh();
    }

    var name = (this.isUp) ? mxEvent.UP : mxEvent.DOWN;
    this.view.fireEvent(new mxEventObject(name,
        'root', this.view.currentRoot, 'previous', this.previous));
    this.isUp = !this.isUp;
};